﻿using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Media;
using Notoric.ViewModel.Internal;

namespace Notoric.ViewModel
{
    /// <summary>
    /// Represents a directory.
    /// </summary>
    public class Directory : IProjectExplorerItem
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Directory"/> instance.
        /// </summary>
        /// <param name="parent">
        /// <para>
        /// Type: <see cref="IProjectExplorerItem"/>
        /// </para>
        /// <para>
        /// The parent of this directory.
        /// </para>
        /// </param>
        /// <param name="name">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name of this directory.
        /// </para>
        /// </param>
        /// <param name="contextMenu">
        /// <para>
        /// Type: <see cref="ContextMenu"/>
        /// </para>
        /// <para>
        /// The context menu for this directory.
        /// </para>
        /// </param>
        public Directory(IProjectExplorerItem parent, string name, ContextMenu contextMenu)
        {
            this.parent = parent;
            this.name = name;
            children = new ObservableCollection<IProjectExplorerItem>();
            children.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(children_CollectionChanged);

            if (contextMenu != null)
            {
                this.contextMenu = new ContextMenu();
                foreach (var item in contextMenu.Items)
                {
                    if (item is MenuItem)
                    {
                        var source = item as MenuItem;
                        var mitem = new MenuItem();

                        mitem.Icon = source.Icon;
                        mitem.Header = source.Header;
                        mitem.Command = source.Command;
                        mitem.CommandParameter = this;

                        this.contextMenu.Items.Add(mitem);
                    }
                    else if (item is Separator)
                        this.contextMenu.Items.Add(new Separator());
                }
            }
        }

        #endregion

        #region IProjectExplorerItem Members

        /// <inheritdoc/>
        public ImageSource Icon
        {
            get
            {
                if (isExpanded)
                    return iconExpanded;
                else
                    return iconCollapsed;
            }
        }

        /// <inheritdoc/>
        public ContextMenu ContextMenu
        {
            get { return contextMenu; }
        }

        /// <inheritdoc/>
        public string DisplayName
        {
            get { return Name; }
        }

        /// <inheritdoc/>
        public bool IsExpanded
        {
            get
            {
                return isExpanded;
            }
            set
            {
                isExpanded = value;
                if (isExpanded && parent != null && !parent.IsExpanded)
                    parent.IsExpanded = true;

                RaisePropertyChanged("IsExpanded");
                RaisePropertyChanged("Icon");
            }
        }

        /// <inheritdoc/>
        public bool IsInEditMode
        {
            get { return isInEditMode; }
            set 
            { 
                isInEditMode = value;

                RaisePropertyChanged("IsInEditMode");
            }
        }

        /// <inheritdoc/>
        public bool IsSelected
        {
            get
            {
                return isSelected;
            }
            set
            {
                isSelected = value;
                if (isSelected && parent != null && !parent.IsExpanded)
                    parent.IsExpanded = true;

                RaisePropertyChanged("IsExpanded");
            }
        }

        /// <inheritdoc/>
        public string Name
        {
            get { return name; }
            set
            {
                var project = ProjectExplorerItemHelper.GetProject(this);
                var cmd = new RenameDirectoryCommand(project.UndoStack, project.RedoStack)
                {
                    RenamedDirectory = this,
                    DirectoryName = value
                };
                cmd.Execute(null);

                RaisePropertyChanged("Name");
                RaisePropertyChanged("DisplayName");
            }
        }

        /// <inheritdoc/>
        public IProjectExplorerItem Parent
        {
            get { return parent; }
            set
            {
                parent = value;
                RaisePropertyChanged("Parent");
            }
        }

        /// <inheritdoc/>
        public ObservableCollection<IProjectExplorerItem> Children
        {
            get { return children; }
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when the value of some property has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the <see cref="children"/> collection has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler modifies the <see cref="IProjectExplorerItem.Parent"/> property of the added / removed items.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void children_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (IProjectExplorerItem item in e.NewItems)
                    item.Parent = this;
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (IProjectExplorerItem item in e.OldItems)
                    item.Parent = null;
            }
            else if (e.Action == NotifyCollectionChangedAction.Replace)
            {
                foreach (IProjectExplorerItem item in e.OldItems)
                    item.Parent = null;
                foreach (IProjectExplorerItem item in e.NewItems)
                    item.Parent = this;
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Gets the virtual path that coresponds to this directory.
        /// </summary>
        /// <returns>The virtual path</returns>
        private string GetVirtualPath()
        {
            if (Parent != null && Parent is Directory)
                return ((Directory)Parent).GetVirtualPath() + "/" + Name;

            return "";
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event on this <see cref="Directory"/> instance.
        /// </summary>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// Name of the property whose value has changed.
        /// </para>
        /// </param>
        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Sets the name of this directory.
        /// </summary>
        /// <remarks>
        /// This method was added because setting the <see cref="Name"/>
        /// property executes a <see cref="RenameDirectoryCommand"/>
        /// that needs some way to set the name string without ending
        /// in a infinite recursion.
        /// </remarks>
        /// <param name="name">
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The name of the directory.
        /// </para>
        /// </param>
        internal void SetName(string name)
        {
            this.name = name;

            RaisePropertyChanged("Name");
            RaisePropertyChanged("DisplayName");
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the virtual path that corresponds to this directory.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The virtual path that corresponds to this directory.
        /// </para>
        /// </value>
        public string VirtualPath
        {
            get { return GetVirtualPath(); }
        }

        #endregion  

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="ContextMenu"/> property.
        /// </summary>
        private ContextMenu contextMenu;
        /// <summary>
        /// Holds the value of the <see cref="Icon"/> property for a collapsed directory.
        /// </summary>
        private static readonly ImageSource iconCollapsed = EmbeddedBitmapLoader.Load("Pictures.ProjectExplorer.Folder_Closed.png");
        /// <summary>
        /// Holds the value of the <see cref="Icon"/> property for an expanded directory.
        /// </summary>
        private static ImageSource iconExpanded = EmbeddedBitmapLoader.Load("Pictures.ProjectExplorer.Folder_Open.png");
        /// <summary>
        /// Holds the value of the <see cref="IsExpanded"/> property.
        /// </summary>
        private bool isExpanded;
        /// <summary>
        /// Holds the value of the <see cref="IsInEditMode"/> property.
        /// </summary>
        private bool isInEditMode;
        /// <summary>
        /// Holds the value of the <see cref="IsSelected"/> property.
        /// </summary>
        private bool isSelected;
        /// <summary>
        /// Holds the value of the <see cref="Name"/> property.
        /// </summary>
        private string name;
        /// <summary>
        /// Holds the value of the <see cref="Parent"/> property.
        /// </summary>
        private IProjectExplorerItem parent;
        /// <summary>
        /// Holds the value of the <see cref="Children"/> property.
        /// </summary>
        private ObservableCollection<IProjectExplorerItem> children;

        #endregion
    }
}
